/*
 * %W% %E%
 *
 * Copyright (c) 2006,2010 Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.nio;

import java.security.AccessController;
import java.security.PrivilegedAction;
import sun.misc.Unsafe;
import sun.misc.VM;

/**
 * Access to bits, native and otherwise.
 */

class Bits {				// package-private

    private Bits() { }


    // -- Swapping --

    static short swap(short x) {
	return (short)((x << 8) |
		       ((x >> 8) & 0xff));
    }

    static char swap(char x) {
	return (char)((x << 8) |
		      ((x >> 8) & 0xff));
    }

    static int swap(int x) {
	return (int)((swap((short)x) << 16) |
		     (swap((short)(x >> 16)) & 0xffff));
    }

    static long swap(long x) {
	return (long)(((long)swap((int)(x)) << 32) |
		      ((long)swap((int)(x >> 32)) & 0xffffffffL));
    }


    // -- get/put char --

    static private char makeChar(byte b1, byte b0) {
	return (char)((b1 << 8) | (b0 & 0xff));
    }

    static char getCharL(ByteBuffer bb, int bi) {
	return makeChar(bb._get(bi + 1),
			bb._get(bi + 0));
    }

    static char getCharL(long a) {
	return makeChar(_get(a + 1),
			_get(a + 0));
    }

    static char getCharB(ByteBuffer bb, int bi) {
	return makeChar(bb._get(bi + 0),
			bb._get(bi + 1));
    }

    static char getCharB(long a) {
	return makeChar(_get(a + 0),
			_get(a + 1));
    }

    static char getChar(ByteBuffer bb, int bi, boolean bigEndian) {
	return (bigEndian ? getCharB(bb, bi) : getCharL(bb, bi));
    }

    static char getChar(long a, boolean bigEndian) {
	return (bigEndian ? getCharB(a) : getCharL(a));
    }

    private static byte char1(char x) { return (byte)(x >> 8); }
    private static byte char0(char x) { return (byte)(x >> 0); }

    static void putCharL(ByteBuffer bb, int bi, char x) {
	bb._put(bi + 0, char0(x));
	bb._put(bi + 1, char1(x));
    }

    static void putCharL(long a, char x) {
	_put(a + 0, char0(x));
	_put(a + 1, char1(x));
    }

    static void putCharB(ByteBuffer bb, int bi, char x) {
	bb._put(bi + 0, char1(x));
	bb._put(bi + 1, char0(x));
    }

    static void putCharB(long a, char x) {
	_put(a + 0, char1(x));
	_put(a + 1, char0(x));
    }

    static void putChar(ByteBuffer bb, int bi, char x, boolean bigEndian) {
	if (bigEndian)
	    putCharB(bb, bi, x);
	else
	    putCharL(bb, bi, x);
    }

    static void putChar(long a, char x, boolean bigEndian) {
	if (bigEndian)
	    putCharB(a, x);
	else
	    putCharL(a, x);
    }


    // -- get/put short --

    static private short makeShort(byte b1, byte b0) {
	return (short)((b1 << 8) | (b0 & 0xff));
    }

    static short getShortL(ByteBuffer bb, int bi) {
	return makeShort(bb._get(bi + 1),
			 bb._get(bi + 0));
    }

    static short getShortL(long a) {
	return makeShort(_get(a + 1),
			 _get(a));
    }

    static short getShortB(ByteBuffer bb, int bi) {
	return makeShort(bb._get(bi + 0),
			 bb._get(bi + 1));
    }

    static short getShortB(long a) {
	return makeShort(_get(a),
			 _get(a + 1));
    }

    static short getShort(ByteBuffer bb, int bi, boolean bigEndian) {
	return (bigEndian ? getShortB(bb, bi) : getShortL(bb, bi));
    }

    static short getShort(long a, boolean bigEndian) {
	return (bigEndian ? getShortB(a) : getShortL(a));
    }

    private static byte short1(short x) { return (byte)(x >> 8); }
    private static byte short0(short x) { return (byte)(x >> 0); }

    static void putShortL(ByteBuffer bb, int bi, short x) {
	bb._put(bi + 0, short0(x));
	bb._put(bi + 1, short1(x));
    }

    static void putShortL(long a, short x) {
	_put(a, short0(x));
	_put(a + 1, short1(x));
    }

    static void putShortB(ByteBuffer bb, int bi, short x) {
	bb._put(bi + 0, short1(x));
	bb._put(bi + 1, short0(x));
    }

    static void putShortB(long a, short x) {
	_put(a, short1(x));
	_put(a + 1, short0(x));
    }

    static void putShort(ByteBuffer bb, int bi, short x, boolean bigEndian) {
	if (bigEndian)
	    putShortB(bb, bi, x);
	else
	    putShortL(bb, bi, x);
    }

    static void putShort(long a, short x, boolean bigEndian) {
	if (bigEndian)
	    putShortB(a, x);
	else
	    putShortL(a, x);
    }


    // -- get/put int --

    static private int makeInt(byte b3, byte b2, byte b1, byte b0) {
	return (int)((((b3 & 0xff) << 24) |
		      ((b2 & 0xff) << 16) |
		      ((b1 & 0xff) <<  8) |
		      ((b0 & 0xff) <<  0)));
    }

    static int getIntL(ByteBuffer bb, int bi) {
	return makeInt(bb._get(bi + 3),
		       bb._get(bi + 2),
		       bb._get(bi + 1),
		       bb._get(bi + 0));
    }

    static int getIntL(long a) {
	return makeInt(_get(a + 3),
		       _get(a + 2),
		       _get(a + 1),
		       _get(a + 0));
    }

    static int getIntB(ByteBuffer bb, int bi) {
	return makeInt(bb._get(bi + 0),
		       bb._get(bi + 1),
		       bb._get(bi + 2),
		       bb._get(bi + 3));
    }

    static int getIntB(long a) {
	return makeInt(_get(a + 0),
		       _get(a + 1),
		       _get(a + 2),
		       _get(a + 3));
    }

    static int getInt(ByteBuffer bb, int bi, boolean bigEndian) {
	return (bigEndian ? getIntB(bb, bi) : getIntL(bb, bi));
    }

    static int getInt(long a, boolean bigEndian) {
	return (bigEndian ? getIntB(a) : getIntL(a));
    }

    private static byte int3(int x) { return (byte)(x >> 24); }
    private static byte int2(int x) { return (byte)(x >> 16); }
    private static byte int1(int x) { return (byte)(x >>  8); }
    private static byte int0(int x) { return (byte)(x >>  0); }

    static void putIntL(ByteBuffer bb, int bi, int x) {
	bb._put(bi + 3, int3(x));
	bb._put(bi + 2, int2(x));
	bb._put(bi + 1, int1(x));
	bb._put(bi + 0, int0(x));
    }

    static void putIntL(long a, int x) {
	_put(a + 3, int3(x));
	_put(a + 2, int2(x));
	_put(a + 1, int1(x));
	_put(a + 0, int0(x));
    }

    static void putIntB(ByteBuffer bb, int bi, int x) {
	bb._put(bi + 0, int3(x));
	bb._put(bi + 1, int2(x));
	bb._put(bi + 2, int1(x));
	bb._put(bi + 3, int0(x));
    }

    static void putIntB(long a, int x) {
	_put(a + 0, int3(x));
	_put(a + 1, int2(x));
	_put(a + 2, int1(x));
	_put(a + 3, int0(x));
    }

    static void putInt(ByteBuffer bb, int bi, int x, boolean bigEndian) {
	if (bigEndian)
	    putIntB(bb, bi, x);
	else
	    putIntL(bb, bi, x);
    }

    static void putInt(long a, int x, boolean bigEndian) {
	if (bigEndian)
	    putIntB(a, x);
	else
	    putIntL(a, x);
    }


    // -- get/put long --

    static private long makeLong(byte b7, byte b6, byte b5, byte b4,
				 byte b3, byte b2, byte b1, byte b0)
    {
	return ((((long)b7 & 0xff) << 56) |
		(((long)b6 & 0xff) << 48) |
		(((long)b5 & 0xff) << 40) |
		(((long)b4 & 0xff) << 32) |
		(((long)b3 & 0xff) << 24) |
		(((long)b2 & 0xff) << 16) |
		(((long)b1 & 0xff) <<  8) |
		(((long)b0 & 0xff) <<  0));
    }

    static long getLongL(ByteBuffer bb, int bi) {
	return makeLong(bb._get(bi + 7),
			bb._get(bi + 6),
			bb._get(bi + 5),
			bb._get(bi + 4),
			bb._get(bi + 3),
			bb._get(bi + 2),
			bb._get(bi + 1),
			bb._get(bi + 0));
    }

    static long getLongL(long a) {
	return makeLong(_get(a + 7),
			_get(a + 6),
			_get(a + 5),
			_get(a + 4),
			_get(a + 3),
			_get(a + 2),
			_get(a + 1),
			_get(a + 0));
    }

    static long getLongB(ByteBuffer bb, int bi) {
	return makeLong(bb._get(bi + 0),
			bb._get(bi + 1),
			bb._get(bi + 2),
			bb._get(bi + 3),
			bb._get(bi + 4),
			bb._get(bi + 5),
			bb._get(bi + 6),
			bb._get(bi + 7));
    }

    static long getLongB(long a) {
	return makeLong(_get(a + 0),
			_get(a + 1),
			_get(a + 2),
			_get(a + 3),
			_get(a + 4),
			_get(a + 5),
			_get(a + 6),
			_get(a + 7));
    }

    static long getLong(ByteBuffer bb, int bi, boolean bigEndian) {
	return (bigEndian ? getLongB(bb, bi) : getLongL(bb, bi));
    }

    static long getLong(long a, boolean bigEndian) {
	return (bigEndian ? getLongB(a) : getLongL(a));
    }

    private static byte long7(long x) { return (byte)(x >> 56); }
    private static byte long6(long x) { return (byte)(x >> 48); }
    private static byte long5(long x) { return (byte)(x >> 40); }
    private static byte long4(long x) { return (byte)(x >> 32); }
    private static byte long3(long x) { return (byte)(x >> 24); }
    private static byte long2(long x) { return (byte)(x >> 16); }
    private static byte long1(long x) { return (byte)(x >>  8); }
    private static byte long0(long x) { return (byte)(x >>  0); }

    static void putLongL(ByteBuffer bb, int bi, long x) {
	bb._put(bi + 7, long7(x));
	bb._put(bi + 6, long6(x));
	bb._put(bi + 5, long5(x));
	bb._put(bi + 4, long4(x));
	bb._put(bi + 3, long3(x));
	bb._put(bi + 2, long2(x));
	bb._put(bi + 1, long1(x));
	bb._put(bi + 0, long0(x));
    }

    static void putLongL(long a, long x) {
	_put(a + 7, long7(x));
	_put(a + 6, long6(x));
	_put(a + 5, long5(x));
	_put(a + 4, long4(x));
	_put(a + 3, long3(x));
	_put(a + 2, long2(x));
	_put(a + 1, long1(x));
	_put(a + 0, long0(x));
    }

    static void putLongB(ByteBuffer bb, int bi, long x) {
	bb._put(bi + 0, long7(x));
	bb._put(bi + 1, long6(x));
	bb._put(bi + 2, long5(x));
	bb._put(bi + 3, long4(x));
	bb._put(bi + 4, long3(x));
	bb._put(bi + 5, long2(x));
	bb._put(bi + 6, long1(x));
	bb._put(bi + 7, long0(x));
    }

    static void putLongB(long a, long x) {
	_put(a + 0, long7(x));
	_put(a + 1, long6(x));
	_put(a + 2, long5(x));
	_put(a + 3, long4(x));
	_put(a + 4, long3(x));
	_put(a + 5, long2(x));
	_put(a + 6, long1(x));
	_put(a + 7, long0(x));
    }

    static void putLong(ByteBuffer bb, int bi, long x, boolean bigEndian) {
	if (bigEndian)
	    putLongB(bb, bi, x);
	else
	    putLongL(bb, bi, x);
    }

    static void putLong(long a, long x, boolean bigEndian) {
	if (bigEndian)
	    putLongB(a, x);
	else
	    putLongL(a, x);
    }


    // -- get/put float --

    static float getFloatL(ByteBuffer bb, int bi) {
	return Float.intBitsToFloat(getIntL(bb, bi));
    }

    static float getFloatL(long a) {
	return Float.intBitsToFloat(getIntL(a));
    }

    static float getFloatB(ByteBuffer bb, int bi) {
	return Float.intBitsToFloat(getIntB(bb, bi));
    }

    static float getFloatB(long a) {
	return Float.intBitsToFloat(getIntB(a));
    }

    static float getFloat(ByteBuffer bb, int bi, boolean bigEndian) {
	return (bigEndian ? getFloatB(bb, bi) : getFloatL(bb, bi));
    }

    static float getFloat(long a, boolean bigEndian) {
	return (bigEndian ? getFloatB(a) : getFloatL(a));
    }

    static void putFloatL(ByteBuffer bb, int bi, float x) {
	putIntL(bb, bi, Float.floatToRawIntBits(x));
    }

    static void putFloatL(long a, float x) {
	putIntL(a, Float.floatToRawIntBits(x));
    }

    static void putFloatB(ByteBuffer bb, int bi, float x) {
	putIntB(bb, bi, Float.floatToRawIntBits(x));
    }

    static void putFloatB(long a, float x) {
	putIntB(a, Float.floatToRawIntBits(x));
    }

    static void putFloat(ByteBuffer bb, int bi, float x, boolean bigEndian) {
	if (bigEndian)
	    putFloatB(bb, bi, x);
	else
	    putFloatL(bb, bi, x);
    }

    static void putFloat(long a, float x, boolean bigEndian) {
	if (bigEndian)
	    putFloatB(a, x);
	else
	    putFloatL(a, x);
    }


    // -- get/put double --

    static double getDoubleL(ByteBuffer bb, int bi) {
	return Double.longBitsToDouble(getLongL(bb, bi));
    }

    static double getDoubleL(long a) {
	return Double.longBitsToDouble(getLongL(a));
    }

    static double getDoubleB(ByteBuffer bb, int bi) {
	return Double.longBitsToDouble(getLongB(bb, bi));
    }

    static double getDoubleB(long a) {
	return Double.longBitsToDouble(getLongB(a));
    }

    static double getDouble(ByteBuffer bb, int bi, boolean bigEndian) {
	return (bigEndian ? getDoubleB(bb, bi) : getDoubleL(bb, bi));
    }

    static double getDouble(long a, boolean bigEndian) {
	return (bigEndian ? getDoubleB(a) : getDoubleL(a));
    }

    static void putDoubleL(ByteBuffer bb, int bi, double x) {
	putLongL(bb, bi, Double.doubleToRawLongBits(x));
    }

    static void putDoubleL(long a, double x) {
	putLongL(a, Double.doubleToRawLongBits(x));
    }

    static void putDoubleB(ByteBuffer bb, int bi, double x) {
	putLongB(bb, bi, Double.doubleToRawLongBits(x));
    }

    static void putDoubleB(long a, double x) {
	putLongB(a, Double.doubleToRawLongBits(x));
    }

    static void putDouble(ByteBuffer bb, int bi, double x, boolean bigEndian) {
	if (bigEndian)
	    putDoubleB(bb, bi, x);
	else
	    putDoubleL(bb, bi, x);
    }

    static void putDouble(long a, double x, boolean bigEndian) {
	if (bigEndian)
	    putDoubleB(a, x);
	else
	    putDoubleL(a, x);
    }


    // -- Unsafe access --

    private static final Unsafe unsafe = Unsafe.getUnsafe();

    private static byte _get(long a) {
	return unsafe.getByte(a);
    }

    private static void _put(long a, byte b) {
	unsafe.putByte(a, b);
    }

    static Unsafe unsafe() {
	return unsafe;
    }


    // -- Processor and memory-system properties --

    private static ByteOrder byteOrder = null;

    static ByteOrder byteOrder() {
	if (byteOrder != null)
	    return byteOrder;
	long a = unsafe.allocateMemory(8);
	try {
	    unsafe.putLong(a, 0x0102030405060708L);
	    byte b = unsafe.getByte(a);
	    switch (b) {
	    case 0x01: byteOrder = ByteOrder.BIG_ENDIAN;     break;
	    case 0x08: byteOrder = ByteOrder.LITTLE_ENDIAN;  break;
	    default:
		throw new Error("Unknown byte order");
	    }
	} finally {
	    unsafe.freeMemory(a);
	}
	return byteOrder;
    }


    private static int pageSize = -1;

    static int pageSize() {
	if (pageSize == -1)
	    pageSize = unsafe().pageSize();
	return pageSize;
    }


    private static boolean unaligned;
    private static boolean unalignedKnown = false;

    static boolean unaligned() {
	if (unalignedKnown)
	    return unaligned;
        PrivilegedAction pa
	    = new sun.security.action.GetPropertyAction("os.arch");
        String arch = (String)AccessController.doPrivileged(pa);
	unaligned = arch.equals("i386") || arch.equals("x86")
	    || arch.equals("amd64");
	unalignedKnown = true;
	return unaligned;
    }


    // -- Direct memory management --

    // A user-settable upper limit on the maximum amount of allocatable
    // direct buffer memory.  This value may be changed during VM
    // initialization if it is launched with "-XX:MaxDirectMemorySize=<size>".
    private static volatile long maxMemory = VM.maxDirectMemory();
    private static volatile long reservedMemory = 0;
    private static boolean memoryLimitSet = false;

    // These methods should be called whenever direct memory is allocated or
    // freed.  They allow the user to control the amount of direct memory
    // which a process may access.  All sizes are specified in bytes.
    static void reserveMemory(long size) {

	synchronized (Bits.class) {
	    if (!memoryLimitSet && VM.isBooted()) {
		maxMemory = VM.maxDirectMemory();
		memoryLimitSet = true;
	    }
	    if (size <= maxMemory - reservedMemory) {
		reservedMemory += size;
		return;
	    }
	}

	System.gc();
	try {
	    Thread.sleep(100);
	} catch (InterruptedException x) {
	    // Restore interrupt status
	    Thread.currentThread().interrupt();
	}
	synchronized (Bits.class) {
	    if (reservedMemory + size > maxMemory)
		throw new OutOfMemoryError("Direct buffer memory");
	    reservedMemory += size;
	}

    }

    static synchronized void unreserveMemory(long size) {
	if (reservedMemory > 0) {
	    reservedMemory -= size;
	    assert (reservedMemory > -1);
	}
    }


    // -- Bulk get/put acceleration --

    // These numbers represent the point at which we have empirically
    // determined that the average cost of a JNI call exceeds the expense
    // of an element by element copy.  These numbers may change over time.
    static final int JNI_COPY_TO_ARRAY_THRESHOLD   = 6;
    static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6;

    // This number limits the number of bytes to copy per call to Unsafe's
    // copyMemory method. A limit is imposed to allow for safepoint polling
    // during a large copy
    static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;

    // These methods do no bounds checking.  Verification that the copy will not
    // result in memory corruption should be done prior to invocation.
    // All positions and lengths are specified in bytes.

    /**
     * Copy from given source array to destination address.
     *
     * @param   src
     *          source array
     * @param   srcBaseOffset
     *          offset of first element of storage in source array
     * @param   srcPos
     *          offset within source array of the first element to read
     * @param   dstAddr
     *          destination address
     * @param   length
     *          number of bytes to copy
     */
    static void copyFromArray(Object src, long srcBaseOffset, long srcPos,
                              long dstAddr, long length)
    {
        long offset = srcBaseOffset + srcPos;
        while (length > 0) {
            long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length;
            unsafe.copyMemory(src, offset, null, dstAddr, size);
            length -= size;
            offset += size;
            dstAddr += size;
        }
    }

    /**
     * Copy from source address into given destination array.
     *
     * @param   srcAddr
     *          source address
     * @param   dst
     *          destination array
     * @param   dstBaseOffset
     *          offset of first element of storage in destination array
     * @param   dstPos
     *          offset within destination array of the first element to write
     * @param   length
     *          number of bytes to copy
     */
    static void copyToArray(long srcAddr, Object dst, long dstBaseOffset, long dstPos,
                            long length)
    {
        long offset = dstBaseOffset + dstPos;
        while (length > 0) {
            long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length;
            unsafe.copyMemory(null, srcAddr, dst, offset, size);
            length -= size;
            srcAddr += size;
            offset += size;
        }
    }

    static void copyFromCharArray(Object src, long srcPos, long dstAddr,
				  long length)
    {
	copyFromShortArray(src, srcPos, dstAddr, length);
    }

    static void copyToCharArray(long srcAddr, Object dst, long dstPos,
				long length)
    {
 	copyToShortArray(srcAddr, dst, dstPos, length);
    }

    static native void copyFromShortArray(Object src, long srcPos, long dstAddr,
					  long length);
    static native void copyToShortArray(long srcAddr, Object dst, long dstPos,
					long length);

    static native void copyFromIntArray(Object src, long srcPos, long dstAddr,
					long length);
    static native void copyToIntArray(long srcAddr, Object dst, long dstPos,
					long length);

    static native void copyFromLongArray(Object src, long srcPos, long dstAddr,
					 long length);
    static native void copyToLongArray(long srcAddr, Object dst, long dstPos,
				       long length);

}
